/*******************************************************************************
 * Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
 * Copyright (c) 2011- kotemaru@kotemaru.org
 ******************************************************************************/
package org.kotemaru.gae.dstool.server;
import com.google.appengine.api.datastore.*;
import com.google.appengine.api.users.User;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;

import org.kotemaru.gae.dstool.DsToolConst;
import org.kotemaru.util.*;

/**
 */
public class EntitySerializer implements DsToolConst{

	private Writer writer;
	private String lastKey;

	public EntitySerializer(Writer w) {
		this.writer = w;
	}

	public String getLastKey() {
		return lastKey;
	}


	/**
	 * EntityからJSONに変換する。
	 * <li>Entity kind の全ての Entity が対象。
	 * @param kind Entity kind
	 * @param offset 開始位置
	 * @param limit  変換するEntityの上限
	 */
	public int serialize(String kind, Key offset, int limit) throws Exception {
		int count = 0;
		Iterator<Entity> ite = iterateEntity(kind,offset, limit);
		while(ite.hasNext()){
			serializeEntity(ite.next());
			count++;
		}
		return count;
	}
	private Iterator<Entity> iterateEntity(String kind, Key offset, int limit) throws Exception {
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

		Query query = new Query(kind);
		if (offset != null) {
			query.addFilter(Entity.KEY_RESERVED_PROPERTY,
						Query.FilterOperator.GREATER_THAN, offset); 
		}
		query.addSort(Entity.KEY_RESERVED_PROPERTY);
		PreparedQuery preQuery = ds.prepare(query);

		FetchOptions op = FetchOptions.Builder.withChunkSize(10);
		op = op.limit(limit);
		return preQuery.asIterator(op);
	}

	private void serializeEntity(Entity entity) throws Exception {
		writer.write("{");
		writer.write(__KEY__+":"+toJson(entity.getKey()));

		Map<String, Object> map = entity.getProperties();
		Iterator<Map.Entry<String, Object>> ite = map.entrySet().iterator();
		while(ite.hasNext()){
			Map.Entry<String, Object> ent = ite.next();
			writer.write(", ");
			writer.write(ent.getKey()+":");
			writer.write(objToJson(ent.getValue()));
		}
		writer.write("},\n");
		writer.flush();

		lastKey = KeyFactory.keyToString(entity.getKey());
	}

	private String objToJson(Object value) throws Exception  {
		if (value == null)  return "null";
		Method m = this.getClass().getMethod("toJson", new Class[]{value.getClass()});
		if (m == null) {
			throw new RuntimeException("Unsupported "+value.getClass().getName());
		}
		return (String) m.invoke(this, value);
	}


	public String toJson(String val)  {return "\""+escStr(val)+"\"";}
	public String toJson(Long val)    {return val.toString();}
	public String toJson(Double val)   {return val.toString();}
	public String toJson(Boolean val) {return val.toString();}
	public String toJson(Date val)    {
		return "{type:\""+DATE+"\",value:"+((Date)val).getTime()+"}";
	}
	public String toJson(Key key) {
		StringBuffer sbuf = new StringBuffer();
		sbuf.append("{type:\"").append(KEY)
			.append("\",kind:\"").append(key.getKind()).append("\"");

		if (key.getName() != null) {
			sbuf.append(",name:\"").append(key.getName()).append("\"");
		} else {
			sbuf.append(",id:").append(key.getId());
		}
		if (key.getParent() != null) {
			sbuf.append(",parent:").append(toJson(key.getParent()));
		}
		sbuf.append("}");
		return sbuf.toString();
	}

	public String toJson(ShortBlob val) {
		return toTypeValue(SBLOB, HexUtil.encodeHex(((ShortBlob)val).getBytes()) );
	}
	public String toJson(Blob val) {
		return toTypeValue(BLOB, HexUtil.encodeHex(((Blob)val).getBytes()) );
	}
	public String toJson(Text val) {
		return toTypeValue(TEXT, escStr(((Text)val).getValue()) );
	}
	public String toJson(Link val) {
		return toTypeValue(LINK, escStr(((Link)val).getValue()) );
	}
	public String toJson(User user) {
		return "{type:\""+USER+"\", email:\""+user.getEmail()
					+"\", authDomain:\""+user.getAuthDomain()+"\"}";
	}
	public String toJson(Collection<?> val) throws Exception  {
		if (val.size() == 0) return "[]";

		StringBuffer sbuf = new StringBuffer();
		sbuf.append("[");
		Iterator<?> ite = val.iterator();
		while(ite.hasNext()){
			sbuf.append(objToJson(ite.next()));
			sbuf.append(",");
		}
		sbuf.setLength(sbuf.length()-1);
		sbuf.append("]");
		return sbuf.toString();
	}


	private static String toTypeValue(String type, String val) {
		StringBuffer sbuf = new StringBuffer();
		sbuf.append("{type:\"").append(type).append("\"");
		sbuf.append(",value:\"").append(val).append("\"");
		sbuf.append("}");
		return sbuf.toString();
	}

	private static String escStr(String str) {
		str = str.replaceAll("[\\\\]","\\\\\\\\")
			.replaceAll("\\n","\\\\\\n")
			.replaceAll("\\r","\\\\\\r")
			.replaceAll("[\"]","\\\\\"")
		;
		return str;
	}

}